/**
 *    Copyright (C) 2008, 2009  Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */


package ru.teleology.cubefs.api.reflection;

import java.util.HashMap;
import java.util.Map;

import ru.teleology.cubefs.api.models.AbstractJavaLogHandler;
import ru.teleology.cubefs.api.models.Container;
import ru.teleology.cubefs.api.models.DefaultLogHandler;
import ru.teleology.cubefs.api.models.IdxMapIterator;
import ru.teleology.cubefs.api.models.InputStreamAdaptor;
import ru.teleology.cubefs.api.models.Iterator;
import ru.teleology.cubefs.api.models.Logger;
import ru.teleology.cubefs.api.models.Manager;
import ru.teleology.cubefs.api.models.Model;
import ru.teleology.cubefs.api.models.OutputStreamAdaptor;
import ru.teleology.cubefs.api.models.Page;
import ru.teleology.cubefs.api.models.StreamManager;
import ru.teleology.cubefs.api.models.TreeMapIterator;
import ru.teleology.cubefs.api.models.Vector;
import ru.teleology.cubefs.api.reflection.metadata.BitmapField;
import ru.teleology.cubefs.api.reflection.metadata.ContainerMetadata;
import ru.teleology.cubefs.api.reflection.metadata.FieldMetadata;
import ru.teleology.cubefs.api.reflection.metadata.FlagField;
import ru.teleology.cubefs.api.reflection.metadata.IDField;
import ru.teleology.cubefs.api.reflection.metadata.Metadata;
import ru.teleology.cubefs.api.reflection.metadata.MetadataGroup;
import ru.teleology.cubefs.api.reflection.metadata.ModelMetadata;
import ru.teleology.cubefs.api.reflection.metadata.NumberField;
import ru.teleology.cubefs.api.reflection.metadata.PageMetadata;

@NativeTypes(types = {
		Type.class, 
		Typed.class, 
		Operation.class, 
		Parameter.class, 
		
		TypeList.class,
		TypedList.class,
		StaticTypedList.class,
		LongList.class,
		
		IDValue.class,
		Number.class,
		StringRef.class,
		StringValue.class,
		VoidPtr.class,
		
		ContainerMetadata.class,
		FieldMetadata.class,
		NumberField.class,
		BitmapField.class,
		IDField.class,
		FlagField.class,
		Metadata.class,
		MetadataGroup.class,
		ModelMetadata.class,
		PageMetadata.class,
		
		Page.class,
		Manager.class,
		StreamManager.class,
		Model.class,
		Container.class,
		Iterator.class,
		TreeMapIterator.class,
		IdxMapIterator.class,
		ru.teleology.cubefs.api.models.Map.class,
		ru.teleology.cubefs.api.models.Collection.class,
		ru.teleology.cubefs.api.models.Map.Pair.class,
		ru.teleology.cubefs.api.models.TreeMap.class,
		ru.teleology.cubefs.api.models.IdxMap.class,
		ru.teleology.cubefs.api.models.blobmap.Data.class,
		ru.teleology.cubefs.api.models.blobmap.DataIterator.class,
		ru.teleology.cubefs.api.models.blobmap.DynVector.class,
		ru.teleology.cubefs.api.models.blobmap.BlobMap.class,
		ru.teleology.cubefs.api.models.blobmap.BlobMapIterator.class,
		Vector.class,
		InputStreamAdaptor.class,
		OutputStreamAdaptor.class,
		DefaultLogHandler.class,
		AbstractJavaLogHandler.class,
		Logger.class
})
public class TypeSystem {
	
	private static boolean initialized = false;
	private static final Map<String, Class<?>> mapping = new HashMap<String, Class<?>>();
	
	static {
		InitTypes();
	}
	
	public static void InitTypes() {
		if (!initialized) {
			System.load("/home/victor/cb-projects2/trunk/bindings/java/dist/"+"libcubefs-core-java.x86_64.so");
			
			InitTypeSystem();
			InitStreams(InputStreamAdaptor.class, OutputStreamAdaptor.class);
			InitLogHandler(AbstractJavaLogHandler.class);
			
			BuildMapping();
			
			Types.init();
			Typed.initNotifier();
			
			for (Type type : list()) {
				type.buildProxies();
			}
			
			initialized = true;
		}
	}
	
	private static void BuildMapping() {
		NativeTypes types = TypeSystem.class.getAnnotation(NativeTypes.class);
		for (Class<?> type: types.types()) {
			NativeType ntype = type.getAnnotation(NativeType.class);
			if (ntype != null) {
				mapping.put(ntype.name(), type);
			}
			else {
				System.out.println("Class "+type+" has no annotation");
			}
		}
	}

	public static void DestroyTypes() {
		if (initialized) {
			DestroyTypeSystem();
			CleanStreams();
			mapping.clear();
			initialized = false;
		}
	}
	
	public static Type find(String name) {
		long handle = FindType(name);
		if (handle != 0) {
			return (Type) Types.getInstance(handle, Type.class);
		}
		else {
			throw new RuntimeException("Native type "+name+" is not found in the type registry");
		}
	}
	
	public static Type find(Class<?> clazz) {
		NativeType ntype = clazz.getAnnotation(NativeType.class);
		if (ntype != null) {
			return find(ntype.name());
		}
		else {
			throw new RuntimeException("NativeType annotaition is not defined for "+clazz);
		} 
	}
	
	public static TypeList list() {
		return (TypeList) Types.getInstance(ListTypes(), TypeList.class);
	}
	
	private static native void InitTypeSystem();
	private static native void DestroyTypeSystem();
	private static native long FindType(String name);	
	private static native long ListTypes();
	
	private static native void InitStreams(Class<?> inClass, Class<?> outClass);
	private static native void InitLogHandler(Class<?> handlerClass);
	private static native void CleanStreams();
	
	public static Class<?> findJavaClass(Type type) {
		Class<?> clazz = mapping.get(type.name());
		if (clazz != null) {
			return clazz;
		}
		else if (type == Types.VoidType()) {
			return void.class;
		}
		else {
			Type parent = type.parent();
			if (parent != null) {
				return findJavaClass(parent);
			}
			else {
				return null;
			}
		}
	}

	public static Map<String, Class<?>> getMapping() {
		return mapping;
	}
}
